//
//  TestLocationModel.m
//  WiteBox
//
//  Created by John Blanco on 6/26/09.
//  Copyright 2009 Effective UI. All rights reserved.
//

#import "TestLocationModel.h"

#import "TestCase.h"
#import "TestCaseRun.h"
#import "TestRun.h"
#import "AssertionException.h";

static TestLocationModel *gInstance = nil;
static const NSInteger kTestsPerCaseMax = 4096;

@implementation TestLocationModel

+ (id)sharedInstance {
	if (!gInstance) {
		gInstance = [[TestLocationModel alloc] init];
	}
	
	return gInstance;
}

- (void)dealloc {
	[testCaseClasses release];
	[testCaseRuns release];
	[callbackTarget release];
	[super dealloc];
}

- (id)setTarget:(id)target action:(SEL)action {
	self = [super init];
	
	if (self) {
		callbackTarget = [target retain];
		callbackAction = action;
		testCaseRuns = [[NSMutableArray alloc] init];
	}
	
	return self;
}

- (void)collectAllTestCaseClasses {
	NSInteger numTotalClasses;
	Class *allClasses;
	BOOL isDescendantOfTestCase;
	
	// count up the total classes
	numTotalClasses = objc_getClassList(nil, 0);
	
	testCaseClasses = [NSMutableArray array];
	
	allClasses = malloc(sizeof(Class) * numTotalClasses);
	numTotalClasses = objc_getClassList(allClasses, numTotalClasses);
	
	for (NSInteger i=0; i < numTotalClasses; ++i) {
		Class iClass = allClasses[i];

		// check ancestry for TestCase (but not the class itself)
		isDescendantOfTestCase = [self isDescendantOfTestCase:class_getSuperclass(iClass)];
		
		// instantiate
		if (isDescendantOfTestCase) {
			// add class
			[testCaseClasses addObject:iClass];
		}
	}
	
	free(allClasses);
}

- (void)scheduleAllTestCases {
	for (Class iClass in testCaseClasses) {
		TestCaseRun *testCaseRun = [[TestCaseRun alloc] initWithClass:iClass];
		
		// allocate for tests
		Method *testMethods = malloc(sizeof(Method) * kTestsPerCaseMax);

		// instantiate test case
		TestCase *testCase = (TestCase *)class_createInstance(iClass, 0);

		// find all test methods
		NSInteger numTestMethods = [self findTestMethods:testMethods inClass:iClass];		
		
		if ([testCase isEnabled]) {
			for (NSInteger i=0; i < numTestMethods; ++i) {
				Method iMethod = testMethods[i];
				
				TestRun *testRun = [[TestRun alloc] initWithSelector:method_getName(iMethod)];

					
				[testCaseRun addTestRun:testRun];
				
				[testRun release];
			}

			[testCaseRuns addObject:testCaseRun];

			free(testMethods);
		}
		
		free(testCase);
	}
}

- (void)runAllTestCases {
	for (TestCaseRun *iTestCaseRun in testCaseRuns) {
		// run tests
		[iTestCaseRun runTests];
		
		// callback
		[callbackTarget performSelectorOnMainThread:callbackAction withObject:[NSNumber numberWithBool:NO] waitUntilDone:NO];
	}

	// callback
	[callbackTarget performSelectorOnMainThread:callbackAction withObject:[NSNumber numberWithBool:YES] waitUntilDone:NO];
}

- (NSArray *)testCaseRuns {
	return testCaseRuns;
}

- (BOOL)isDescendantOfTestCase:(Class)aClass {
	if (aClass == nil) {
		return NO;
	} else if (strcmp(class_getName(aClass), "TestCase") == 0) {
		return YES;
	} else {
		return [self isDescendantOfTestCase:class_getSuperclass(aClass)];
	}
}

- (NSInteger)findTestMethods:(Method *)methods inClass:(Class)aClass {
	unsigned int methodCount;
	NSInteger numMethods = 0;
	
	while (aClass) {
		Method *allMethods = class_copyMethodList(aClass, &methodCount);
		
		for (NSInteger i=0; i < methodCount; ++i) {
			Method method = allMethods[i];
			SEL sel = method_getName(method);

			// test methods must take no real arguments and begin with testXXX
			// TODO: also check return value
			if ([NSStringFromSelector(sel) hasPrefix:@"test"] && method_getNumberOfArguments(method) == 2) {
				methods[numMethods++] = method;
			}
		}
		
		aClass = class_getSuperclass(aClass);
	}
	
	return numMethods;
}

@end
